# Concurrent futures
import threading
import traceback
from log import log

class ExitingException(Exception):
  pass

class FutureException(Exception):
  def __init__(self, e):
    self._e = e

  def message(self):
    return self._e

class Future:
  def __init__(self, function):
    self._function = function
    self._finished = False
    self._cond = threading.Condition()
    self._exception = None

  def Wait(self):
    self._cond.acquire()
    while not self._finished:
      self._cond.wait()
    self._cond.release()
    if self._exception:
      raise FutureException(self._exception)
    return self._result

  def Run(self):
    try:
      result = self._function()
      self._cond.acquire()
      self._result = result
    except:
      self._cond.acquire()
      self._exception = traceback.format_exc()

    self._finished = True
    self._cond.notify()
    self._cond.release()

  def Terminal(self):
    if self._function is None:
      log("Terminal future processed")
      self._cond.acquire()
      self._result = None
      self._finished = True
      self._cond.notify()
      self._cond.release()
      return True
    else:
      return False

class Executor:
  def __init__(self, name):
    self._queue = []
    self._cond = threading.Condition()
    self._thread = threading.Thread(name=name)
    self._thread.run = self._Run
    self._thread.start()
    self._terminating = False
    self._terminating_lock = threading.Lock()

  def Add(self, future):
    self._terminating_lock.acquire()
    if self._terminating:
      raise ExitingException()
    self.AddImpl(future)
    self._terminating_lock.release()

  def AddImpl(self, future):
    self._cond.acquire()
    self._queue.append(future)
    self._cond.notify()
    self._cond.release()

  def Exit(self):
    log("Executor exiting!")
    future = Future(None)
    self._terminating_lock.acquire()
    self.AddImpl(future)
    self._terminating = True
    self._terminating_lock.release()
    return future

  def _Run(self):
    future = self._GetNextFuture()
    while not future.Terminal():
      future.Run()
      future = self._GetNextFuture()
    log("Executor Run() returning.")

  def _GetNextFuture(self):
    self._cond.acquire()
    while not self._queue:
      self._cond.wait()
    future = self._queue[0]
    self._queue = self._queue[1:]
    self._cond.release()
    return future


